package com.tuling.vipes.service.impl;


import com.tuling.vipes.service.ElasticsearchSelectService;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.client.indices.AnalyzeResponse;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @创建人: Mhh
 * @创建时间: 2021/12/10
 * @描述:
 */
@Service
public class ElasticsearchSelectServiceImp implements ElasticsearchSelectService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    private ElasticsearchOperations elasticsearchOperations;

    /**
     * @param id        : 主键id
     * @param classType : 类对象(用来获取这个对象中的@Document注解中indexName属性[表示查询那个索引])
     * @return T
     * @explain : 根据主键id查询数据
     * @Author Mhh
     * @Date 2021/12/10 10:46
     */
    @Override
    public <T> T get(String id, Class<T> classType) {
        T t = elasticsearchRestTemplate.get(id, classType);
        T t1 = elasticsearchOperations.get(id, classType);
        return t;
    }

    /**
     * @param id        : 主键id
     * @param classType : 类对象(用来获取这个对象中的@Document注解中indexName属性[表示查询那个索引])
     * @return java.lang.Boolean
     * @explain : 判断主键id是否存在
     * @Author Mhh
     * @Date 2021/12/29 14:54
     */

    @Override
    public Boolean  exists(String id, Class<?> classType) {
        return elasticsearchRestTemplate.exists(id, classType);
    }


    /**
     * @param pageNum   : 分页查询的页数
     * @param pageSize  : 分页查询返回的每页个数
     * @param key       : 查询es的字段名
     * @param value     : 要查询字段名中的值
     * @param classType : 返回的类类型
     * @return List<T></T>
     * @throws
     * @Author Mhh
     * @Date 2021/12/9 10:36
     */
    public <T> List<T> selectFindPage(Integer pageNum, Integer pageSize, String key, String value, Class<T> classType) {

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(key, value).analyzer("ik_smart");//分词查询(这里指定分词只是分传过来的参数)[它是要和es索引里的分词后数据一一对应才能返回]
                                                                    /*例子：前台传了 (山东省:粗粒度分为山东省 细粒度分为：山东省,山东,省)
                                                                        es索引库里(山东省济南市 粗粒度分为 山东省,济南市  细粒度分为:山东省,山东,省,济南市,济南,市)
                                                                        只有当前台分的词和后台分的词能有一个匹配上就可以*/
        nativeSearchQueryBuilder.withQuery(matchQueryBuilder);
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum == null || pageNum == 0 ? 0 : pageNum - 1, pageSize));
        //4.构建查询对象
        NativeSearchQuery query = nativeSearchQueryBuilder.build();

        IndexCoordinates of = IndexCoordinates.of(classType.getAnnotation(Document.class).indexName());
        SearchHits<T> search = elasticsearchRestTemplate.search(query, classType, of);
        List<T> result = search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());

        return result;

    }


    /**
     * @param tokenizer : 粗细粒度分词(粗粒度:ik_smart   细粒度:ik_max_word)
     * @param text      : 需要分词的入参
     * @return java.util.List<org.elasticsearch.client.indices.AnalyzeResponse.AnalyzeToken>
     * @throws
     * @explain
     * @Author Mhh
     * @Date 2021/12/9 16:16
     */
    public List<AnalyzeResponse.AnalyzeToken> selectBreakUpText(String tokenizer, String text) throws IOException {
        RestHighLevelClient restHighLevelClient = elasticsearchRestTemplate.execute((client) -> client);
        AnalyzeRequest request = AnalyzeRequest.buildCustomAnalyzer(tokenizer).build(text);
        AnalyzeResponse response = restHighLevelClient.indices().analyze(request, RequestOptions.DEFAULT);
        return response.getTokens();
//-------------------------------这两种都可以---------------------------------------------------------------------
//        AnalyzeRequest request = AnalyzeRequest.buildCustomAnalyzer(tokenizer).build(text);
//        Class<? extends ElasticsearchRestTemplate> aClass = elasticsearchRestTemplate.getClass();//反射
//        Field client = aClass.getDeclaredField("client");//获得 ElasticsearchRestTemplate里的client属性
//        client.setAccessible(true);//私有权限解除
//        RestHighLevelClient restHighLevelClient = (RestHighLevelClient) client.get(elasticsearchRestTemplate);
//        AnalyzeResponse response = restHighLevelClient.indices().analyze(request, RequestOptions.DEFAULT);
//        return response.getTokens();
    }


    /**
     * @param key       : es里索引的域(字段名)
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @param values    : 一域多值, 查询的值
     * @return java.util.List<T>
     * @explain :词条查询(不分前端传过来的数据)
     * @Author Mhh
     * @Date 2021/12/10 14:28
     */
    public <T> List<T> termQuery(String key, Class<T> classType, String... values) {
        //方案1:第一步，查询条件(词条查询：对应ES query里的term)
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(key, values);


        //方案一:第二步，构建DSL中filter查询条件(对应ES大括号里的filter)
        ConstantScoreQueryBuilder constantScoreQueryBuilder = QueryBuilders.constantScoreQuery(termsQueryBuilder);

        //不支持NativeQuery
//        Query query = NativeQuery.builder().
//                withQuery(co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders.term(t -> t.field(key).value("泰安市"))).build();



        //方式1：第三步：创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号) 使用filter 不进行评分或相关度的计算
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(constantScoreQueryBuilder).build();
        //方式2：第二步骤：创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号) 不使用filter
        //NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(termsQueryBuilder).build();
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());

    }


    /**
     * 入参分词: 山东省济南市  ik_smart粗粒度:[山东省,济南市] ik_max_word细粒度:[山东省,山东,省,济南市,济南,南市]
     *
     * @param operator  : Operator.OR(并集) [默认] 只要分的词有一个和索引字段上对应上则就返回
     *                  Operator.AND(交集)   分的词全部满足的数据返回
     * @param analyzer  : 选择分词器[ik_smart粗粒度,ik_max_word细粒度] 默认:ik_max_word细粒度
     * @param key       :  es里索引的域(字段名)
     * @param classType :  返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @param text    :  查询的值
     * @return java.util.List<T>
     * @explain ：matchQuery:词条分词查询(会对查询条件进行分词)
     * @Author Mhh
     * @Date 2021/12/10 15:21
     */
    public <T> List<T> matchQuery(Operator operator, String analyzer, String key, Class<T> classType, String text) {
        //查询条件(词条查询：对应ES query里的match)
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(key, text).analyzer(analyzer).operator(operator);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(matchQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());

    }


    /**
     * @param classType :返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain : 返回索引库里所有数据
     * @Author Mhh
     * @Date 2021/12/10 15:44
     */
    public <T> List<T> matchAllQuery(Class<T> classType) {
        //查询条件(词条查询：对应ES query里的match)
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(matchAllQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());

    }

    /*
     *   ?:只包含一个字符
     *       山?省:  山东省 或者 山西省 等等   ?包含一个字符
     *       ??省：  山东省 或者 吉林省 等等   ?包含一个字符
     *       ???:    你好啊 或者 早上好 等等 ?包含一个字符
     *
     *   *:表示0个或多个字符
     *       济南*： 济南市 或者 济南市历下区.....  *表示0个或多个字符
     *       *剑 ：  长虹剑 或者 冰魄长虹倚天剑.... *表示0个或多个字符
     *
     * 注意: *或者?放在最前面(例如：*省 | ?省    *为 | ?为) 会引发全表(全索引)扫描  注意效率问题
     * */

    /**
     * @param key       :  es里索引的域(字段名)
     * @param value     : 查询的值
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain :   wildcardQuery模糊查询(会对查询条件分词，还可以使用通配符)[?:表示任意单个字符][*:表示0或多个字符]
     * @Author Mhh
     * @Date 2021/12/10 16:10
     */

    public <T> List<T> wildcardQuery(String key, String value, Class<T> classType) {

        //查询条件(词条查询：对应ES query里的wildcard)
        WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery(key, value);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(wildcardQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());

    }

    /**
     * @param key       :  es里索引的域(字段名)
     * @param value     : 查询的值
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain :  prefixQuery 前缀查询 对keyword类型支持比较好(text也能用:索引库字段分词后 分的词前缀要是能匹配也是可以返回此数据)
     * @Author Mhh
     * @Date 2021/12/10 16:49
     */
    public <T> List<T> prefixQuery(String key, String value, Class<T> classType) {

        //查询条件(词条查询：对应ES query里的prefixQuery)
        PrefixQueryBuilder prefixQueryBuilder = QueryBuilders.prefixQuery(key, value);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(prefixQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());

    }

    /**
     * @param key       :  es里索引的域(字段名)
     * @param value     : 查询的值
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain : 正则表达式查询:regexpQuery
     * @Author Mhh
     * @Date 2021/12/11 19:17
     */
    public <T> List<T> regexpQuery(String key, String value, Class<T> classType) {
        //查询条件(词条查询：对应ES query里的regexpQuery)
        RegexpQueryBuilder regexpQueryBuilder = QueryBuilders.regexpQuery(key, value);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(regexpQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }

    /**
     * @param name       :es里索引的域(字段名)
     * @param from    :范围查询值 1 from(Object from, boolean includeLower) includeLower：是否包含 默认为true[包含]
     * //              如果from==null  那么就是<=to的
     * @param to    :范围查询值 2 to(Object to, boolean includeUpper) includeLower：是否包含 默认为true[包含]
     * //              如果to==null 那么就是 >=from的
     * @param classType :返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain : rangeQuery 范围查询
     * @Author Mhh
     * @Date 2021/12/12 18:13
     */
    public <T> List<T> rangeQuery(String name, Integer from, Integer to, Class<T> classType) {

        //查询条件(词条查询：对应ES query里的rangeQuery)
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(name).from(from).to(to);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(rangeQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }


    /**
     * @param direction : 排序 Sort.Direction.ASC:升序  ||  Sort.Direction.DESC:降序
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @param properties       : 域名(可变参数,可传1至多个)
     * @return java.util.List<T>
     * @explain : 给查询 结果排序
     * @Author Mhh
     * @Date 2021/12/12 18:45
     */
    public <T> List<T> sort(Sort.Direction direction, Class<T> classType, String... properties) {
        //查询条件(词条查询：对应ES query里的sort)
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(matchAllQueryBuilder);
        nativeSearchQuery.addSort(Sort.by(direction, properties));
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }


    /*
     *      多域查询的交并集理解:
     *                      OR:  只要有一个域中包含入参value被分词后的"一个值"时就返回
     *                      AND: 只要有一个域中包含入参value被分词后的"所有值"时返回
     *
     * @param fields    : Map<String,Float>类型:key为域名,Float为boost值
     *                  boost: 参数被用来提升一个语句的相对权重（ boost 值大于 1 ）或降低相对权重（ boost 值处于 0 到 1 之间），但是这种提升或降低并不是线性的，换句话说，如果一个 boost 值为 2 ，并不能获得两倍的评分 _score 。
     * @param queryString   : 要查询的值 (会对查询条件进行分词)
     * @param analyzer  : 选择分词器[ik_smart粗粒度,ik_max_word细粒度] 默认:ik_max_word细粒度
     * @param operator  : Operator.OR(并集) [默认] 只要分的词有一个和索引字段上对应上则就返回
     *                  Operator.AND(交集)   分的词全部满足的数据返回
     * @param classType :  返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain :  queryString 多条件查询
     * •会对查询条件进行分词。
     * •然后将分词后的查询条件和词条进行等值匹配
     * •默认取并集（OR）
     * •可以指定多个查询字段
     * •query_string：识别query中的连接符（or 、and）
     * @Author Mhh
     * @Date 2021/12/12 19:45
     */
    public <T> List<T> queryStringQuery(Map<String, Float> fields, String queryString, String analyzer, Operator operator, Class<T> classType) {
        //查询条件(词条查询：对应ES query里的queryStringQuery)
        QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(queryString).fields(fields).analyzer(analyzer).defaultOperator(operator);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryStringQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }


    /**
     * ------boolQuery子句用termsQuery做的测试
     * //               fields: 只要有一条数据包含fields 都包含value数组中的一种就能返回
     *
     * @param fields    :Map<String,String[]> key:es索引库里的域(字段名), value数组:一域多值, 查询的值
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain : boolQuery：返回的文档必须满足must子句的条件,并且参与计算分值
     * //                   条件必须成立，性能比filter低。会计算得分
     * //                   对多个查询条件连接。连接方式：must（and）：条件必须成立(会计算得分)
     * //                   maxSore(得分):即条件匹配度,匹配度越高，得分越高
     * @Author Mhh
     * @Date 2021/12/13 10:31
     */
    public <T> List<T> boolQueryBuilderByMust(Map<String, String[]> fields, Class<T> classType) {
        //构建boolQuery(词条查询：对应ES query里的bool)对多个查询条件连接
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        fields.forEach((key, value1) -> {
            //查询条件(词条查询：对应ES query里的termQuery)
            TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(key, value1);
            //must（and）：条件必须成立
            boolQueryBuilder.must(termsQueryBuilder);
        });

        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(boolQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }

    /**
     * ------boolQuery子句用termsQuery做的测试
     * //               fields: 只要有一条数据包含fields 都包含value数组中的一种就能返回
     *
     * @param fields    :Map<String,String[]> key:es索引库里的域(字段名), value数组:一域多值, 查询的值(must:数组中多值是或者的关系，只要是所有字段能对应到数组中至少一个值就可以返回)
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain : boolQuery：返回的文档必须满足filter子句的条件,但是不会像must一样,参与计算分值
     * //                   对多个查询条件连接。连接方式：filter(and)：条件必须成立，性能比must高。不会计算得分
     * //                   maxSore(得分):即条件匹配度,匹配度越高，得分越高
     * @Author Mhh
     * @Date 2021/12/13 10:31
     */
    public <T> List<T> boolQueryBuilderByFilter(Map<String, String[]> fields, Class<T> classType) {
        //构建boolQuery(词条查询：对应ES query里的bool)对多个查询条件连接
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        fields.forEach((key, value1) -> {
            //查询条件(词条查询：对应ES query里的termQuery)
            TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(key, value1);
            //must（and）：条件必须成立
            boolQueryBuilder.filter(termsQueryBuilder);
        });

        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(boolQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }


    /**
     * 速度要快  输入的内容立即返回  对字段类型要求多节省存储空间  时间复杂度O(1)，做建议不做纠错
     * 感觉和prefixQuery 前缀查询 差不多.....
     * <p>
     * 搜索补全必须定义 这个属性(
     * //                  @CompletionField(analyzer = "ik_smart", searchAnalyzer = "ik_smart", maxInputLength = 100)
     * //                  private Completion completion;)
     * //      给Completion属性赋值: new Completion(new String[]{"山东省泰安市岱岳区"}))
     * //                  :里面的值就是被自动补全的值
     *
     * @param fieldName : 要用哪个字段进行标题联想(必须是这个@CompletionField注解所标注的类型为Completion的字段名)
     * @param text      : 被补全的值(比如传的是山东 可能就能给补全为 山东省)
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<java.lang.String>
     * @explain : 搜索补全功能 比如在输入框输入(天上)下面就自动补全 (天上人间)(天上边的彩霞)(....)
     * @Author Mhh
     * @Date 2021/12/22 16:51
     */
    public List<String> completionSuggestion(String fieldName, String text, Class<?> classType) {
        //定义反参容器
        List<String> stringList = new ArrayList<>();
        //构建搜索建议补全对象
        CompletionSuggestionBuilder completionSuggestionBuilder = SuggestBuilders.
                completionSuggestion(fieldName). // 使用fieldName入参进行标题联想(这里就是索引被@CompletionField注解标注的字段)
                        prefix(text).       // 关键字（参数传此）
                        skipDuplicates(true)// 重复过滤
                //.size(100)       // 匹配数量
                ;
        //创建搜索提示对象 进行封装搜索补全
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        //  completionSuggestionBuilder:随便起的搜索补全的名字
        suggestBuilder.addSuggestion("completionSuggestionBuilder", completionSuggestionBuilder);
        //查询es并反参
        SearchResponse searchResponse = elasticsearchRestTemplate.suggest(suggestBuilder, elasticsearchRestTemplate.getIndexCoordinatesFor(classType));
        //获取反参中的搜索补全结果
        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestionBuilder = searchResponse.getSuggest().getSuggestion("completionSuggestionBuilder");

        // 处理返回
        List<String> suggests = suggestionBuilder.getEntries().stream().map(x -> x.getOptions().stream().map(y -> y.getText().toString()).collect(Collectors.toList())).findFirst().get();
        // 将搜索补全内容保存到容器返回
        for (String suggest : suggests) {
            stringList.add(suggest);
            System.err.println("suggest = " + suggest);
        }
        return stringList;
    }



    /**
     * @param field     : 高亮字段 也是 match要查询的字段
     * @param preTags   : 高亮前缀
     * @param postTags  : 高亮后缀
     * @param text      : 查询的值(会分词)
     * @param classType :  返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain :  给查询到的值进行高亮
     * @Author Mhh
     * @Date 2021/12/21 15:15
     */
    public <T> List<T> highlightBuilder(String field, String preTags, String postTags, String text, Class<T> classType) {
        //查询条件(词条查询：对应ES query里的match)
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(field, text);
        //
        //设置高亮三要素                                    field: 你的高亮字段    // preTags ：前缀    // postTags：后缀
        HighlightBuilder highlightBuilder = new HighlightBuilder().field(field).preTags(preTags).postTags(postTags);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(matchQueryBuilder).withHighlightBuilder(highlightBuilder).build();
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(searchHit -> {
            //获得结果实体
            T content = searchHit.getContent();
            //所有高亮结果
            Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
            //遍历高亮结果
            for (Map.Entry<String, List<String>> stringListEntry : highlightFields.entrySet()) {
                String key = stringListEntry.getKey();
                //获取实体反射类
                Class<?> aClass = content.getClass();
                try {
                    //获取该实体属性
                    Field declaredField = aClass.getDeclaredField(key);
                    //权限为私的 解除！
                    declaredField.setAccessible(true);
                    //替换，把高亮字段替换到这个实体对应的属性值上
                    declaredField.set(content, stringListEntry.getValue().get(0));
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            return content;
        }).collect(Collectors.toList());
    }


    /**
     * ------boolQuery子句用termsQuery做的测试
     *
     * @param fields             :Map<String,String[]> key:es索引库里的域(字段名), value数组:一域多值, 查询的值(should:只要有一条数据对应的字段包含 value数组的值就返回)
     *                           //只要有一条数据对应的字段包含 value数组的值就返回
     * @param minimumShouldMatch : 参数定义了至少满足几个子句
     * @param classType          : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain : boolQuery：返回的文档可能满足should子句的条件.
     * //                   在一个bool查询中,如果没有must或者filter,有一个或者多个should子句,那么只要满足一个就可以返回
     * @Author Mhh
     * @Date 2021/12/19 19:23
     */

    public <T> List<T> boolQueryBuilderByShould(Map<String, String[]> fields, Integer minimumShouldMatch, Class<T> classType) {
        //构建boolQuery(词条查询：对应ES query里的bool)对多个查询条件连接
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        fields.forEach((key, value1) -> {
            //查询条件(词条查询：对应ES query里的termQuery)
            TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(key, value1);
            //must（and）：条件必须成立
            boolQueryBuilder.should(termsQueryBuilder);
        });
        //参数定义了至少满足几个子句
        boolQueryBuilder.minimumShouldMatch(minimumShouldMatch);
        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(boolQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }




    /**
     * ------boolQuery子句用termsQuery做的测试
     *
     * @param fields    :Map<String,String[]> key:es索引库里的域(字段名), value数组:一域多值, 查询的值
     *                  //fields 只要数据不包含在 value数组中指定的返回
     * @param classType : 返回的list里的对象并且通过对象里面@Document注解indexName属性获取查询哪个索引
     * @return java.util.List<T>
     * @explain : boolQuery：返回的文档必须不满足定义的条件
     * //           对多个查询条件连接。连接方式：must_not（not）：条件必须不成立
     * @Author Mhh
     * @Date 2021/12/19 18:55
     */

    public <T> List<T> boolQueryBuilderByMustNot(Map<String, String[]> fields, Class<T> classType) {
        //构建boolQuery(词条查询：对应ES query里的bool)对多个查询条件连接
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        fields.forEach((key, value1) -> {
            //查询条件(词条查询：对应ES query里的termQuery)
            TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(key, value1);
            //must（and）：条件必须成立
            boolQueryBuilder.mustNot(termsQueryBuilder);
        });

        //创建查询条件构建器SearchSourceBuilder(对应ES外面的大括号)
        NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(boolQueryBuilder);
        //查询,获取查询结果
        SearchHits<T> search = elasticsearchRestTemplate.search(nativeSearchQuery, classType);
        //获取总记录数
        long totalHits = search.getTotalHits();
        //获取值返回
        return search.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
    }

}



